using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;
using System.Globalization;

namespace Dida
{
	public enum KeyframesAddressing
	{
		Repeat,
		Clamp,
	}

	public class LinearKeyframes<T>
	{
		public interface IInterpolator
		{
			T Interpolate(T a, T b, float t);
		}

		private struct Key
		{
			public float t;
			public T value;
		}
		Key[] keys;

		KeyframesAddressing addressing = KeyframesAddressing.Clamp;
		IInterpolator interpolator;

		public LinearKeyframes(float[] positions, T[] values,
			KeyframesAddressing addressing, IInterpolator interpolator)
		{
			Debug.Assert(positions.Length == values.Length);

			this.addressing = addressing;
			this.interpolator = interpolator;

			keys = new Key[positions.Length];
			for (int i = 0; i < positions.Length; i++)
			{
				keys[i].t = positions[i];
				keys[i].value = values[i];
			}
		}

		public T Evaluate(float t)
		{
			int lowerKey;
			float localT;
			GetInterpolationParams(t, out lowerKey, out localT);
			return interpolator.Interpolate(keys[lowerKey].value,
				keys[lowerKey + 1].value, localT);
		}

		private int FindLowerKey(float t)
		{			
			int min = -1;
			int max = keys.Length;
			while ((min + 1) < max)
			{
				int cur = (min + max) / 2;
				if (keys[cur].t > t)
					max = cur;
				else if (keys[cur].t < t)
					min = cur;
				else
					return cur;
			}

			return min;
		}

		private static float Modulus(float x, float y)
		{
			return (float)(x - Math.Floor(x / y) * y);
		}

		private void GetInterpolationParams(float t, out int lowerKey, out float localT)
		{
			Debug.Assert(keys.Length >= 2,"Keyframes<T> must at least have 2 keyframes");

			switch (addressing)
			{
				case KeyframesAddressing.Clamp:
					if (t < keys[0].t)
						t = keys[0].t;
					else if (t > keys[keys.Length - 1].t)
						t = keys[keys.Length - 1].t;
					break;

				case KeyframesAddressing.Repeat:
					float range = keys[keys.Length - 1].t - keys[0].t;
					if (t < 0)
						t = -Modulus(-t,range) + range;
					else
						t = Modulus(t, range);
					break;
			}

			lowerKey = FindLowerKey(t);
			if (lowerKey == -1)
			{
				lowerKey = 0;
				localT = 0;
			}
			else if (lowerKey == keys.Length - 1)
			{
				lowerKey = keys.Length - 2;
				localT = 1;
			}
			else
			{
				localT = (t - keys[lowerKey].t) / (keys[lowerKey + 1].t -
					keys[lowerKey].t);
			}
		}

		public class Iterator
		{
			private LinearKeyframes<T> keyframes;

			private int lowerKey;
			private float curT;
			private T curValue;

			internal Iterator(LinearKeyframes<T> keyframes,int lowerKey, float curT)
			{
				this.keyframes = keyframes;				
				this.lowerKey = lowerKey;
				this.curT = curT;
			}

			public void MoveForward(float t)
			{
				curT += t;
				if (curT > keyframes.keys[lowerKey + 1].t)
				{
					lowerKey++;

					if (lowerKey == keyframes.keys.Length - 1)
					{
						switch (keyframes.addressing)
						{
							case KeyframesAddressing.Clamp:
								curT = keyframes.keys[lowerKey].t;
								lowerKey--;
								break;

							case KeyframesAddressing.Repeat:
								lowerKey = 0;
								curT = keyframes.keys[0].t;
								break;
						}
					}
				}

				float localT = (curT - keyframes.keys[lowerKey].t) /
					(keyframes.keys[lowerKey + 1].t - keyframes.keys[lowerKey].t);
				curValue = keyframes.interpolator.Interpolate(keyframes.keys[lowerKey].value,
					keyframes.keys[lowerKey + 1].value, localT);
			}

			public void MoveBackward(float t)
			{
				curT += t;
				if (curT < keyframes.keys[lowerKey + 1].t)
				{
					lowerKey--;

					if (lowerKey == -1)
					{
						switch (keyframes.addressing)
						{
							case KeyframesAddressing.Clamp:
								curT = keyframes.keys[0].t;
								lowerKey = 0;
								break;

							case KeyframesAddressing.Repeat:
								lowerKey = keyframes.keys.Length - 2;
								curT = keyframes.keys[lowerKey + 1].t;
								break;
						}
					}
				}

				float localT = (curT - keyframes.keys[lowerKey].t) /
					(keyframes.keys[lowerKey + 1].t - keyframes.keys[lowerKey].t);

				curValue = keyframes.interpolator.Interpolate(
					keyframes.keys[lowerKey].value,keyframes.keys[lowerKey + 1].value, localT);
			}

			public T CurValue
			{
				get
				{
					return curValue;
				}
			}
		}

		public Iterator GetIterator(float initialPosition)
		{
			float dumLocalT;
			int lowerKey;
			GetInterpolationParams(initialPosition, out lowerKey, out dumLocalT);
			return new Iterator(this,lowerKey, initialPosition);
		}
	}

	public abstract class LinearKeyframesCompiler<T> : Dxml.XmlObjectCompilerBase
	{
		List<float> positions = new List<float>();
		List<T> values = new List<T>();
		KeyframesAddressing addressing;

		public override void  Load(XmlReader reader, Dxml.XmlCompiler compiler)
		{
			ReadDefAttributes(reader);

			string addressingStr = reader.GetAttribute("addressing");
			if (addressingStr == null)
				addressing = KeyframesAddressing.Clamp;
			else
			{
				switch (addressingStr)
				{
					case "Repeat":
						addressing = KeyframesAddressing.Repeat;
						break;

					case "Clamp":
						addressing = KeyframesAddressing.Clamp;
						break;

					default:
						throw new XmlAttributeException("Unknown addressing.", reader, "addressing");
				}
			}
 			
			if(reader.IsEmptyElement)
				throw new XmlReaderException("A keyframes definition shouldn't be empty.",reader);
			reader.ReadStartElement();
			while(reader.NodeType != XmlNodeType.EndElement)
			{
				float t = ReadFloatAttribute(reader,"t");
				positions.Add(t);

				string valStr = ReadStringAttribute(reader,"value");
				try
				{
					values.Add(ParseValue(valStr));
				}
				catch (Exception err)
				{
					throw new XmlAttributeException(err, reader, "value");
				}

				if (reader.IsEmptyElement)
					reader.ReadStartElement();
				else
				{
					reader.ReadStartElement();
					reader.ReadEndElement();
				}
			}
			reader.ReadEndElement();
		}

		protected abstract T ParseValue(string str);

		public override Dxml.CompilingReference[] GetReferences()
		{
 			return new Dxml.CompilingReference[0];
		}

		public override object CreateObject(Dxml.XmlCompiler compiler, object[] resolvedReferences)
		{
			return CreateObject(positions.ToArray(), values.ToArray(), addressing);
		}

		protected abstract LinearKeyframes<T> CreateObject(float[] positions, T[] values, KeyframesAddressing addressing);
	}

	public class FloatLinearKeyframesCompiler : LinearKeyframesCompiler<float>
	{
		protected override float ParseValue(string str)
		{
			return float.Parse(str, NumberStyles.Float);
		}

		protected override LinearKeyframes<float> CreateObject(float[] positions, float[] values, KeyframesAddressing addressing)
		{
			return new FloatLinearKeyframes(positions, values, addressing);
		}

		public override Type ObjectType
		{
			get { return typeof(FloatLinearKeyframes); }
		}
	}

	[Dxml.DidaObject(Compiler = typeof(FloatLinearKeyframesCompiler))]
	public class FloatLinearKeyframes : LinearKeyframes<float>
	{
		public FloatLinearKeyframes(float[] positions, float[] values,
			KeyframesAddressing addressing)
			: base(positions,values,addressing,new FloatInterpolator())
		{
		}

		private class FloatInterpolator : IInterpolator
		{
			public float Interpolate(float a, float b, float t)
			{
				return a + (a - b) * t;
			}
		}
	}

	public class Vector3DLinearKeyframesCompiler : LinearKeyframesCompiler<Vector3D>
	{
		protected override Vector3D ParseValue(string str)
		{
			return Vector3D.Parse(str);
		}

		protected override LinearKeyframes<Vector3D> CreateObject(float[] positions, Vector3D[] values, KeyframesAddressing addressing)
		{
			return new Vector3DLinearKeyframes(positions, values, addressing);
		}

		public override Type ObjectType
		{
			get { return typeof(Vector3DLinearKeyframes); }
		}
	}

	[Dxml.DidaObject(Compiler = typeof(Vector3DLinearKeyframesCompiler))]
	public class Vector3DLinearKeyframes : LinearKeyframes<Vector3D>
	{
		public Vector3DLinearKeyframes(float[] positions, Vector3D[] values,
			KeyframesAddressing addressing)
			: base(positions,values,addressing,new Vector3DInterpolator())
		{
		}

		private class Vector3DInterpolator : IInterpolator
		{
			public Vector3D Interpolate(Vector3D a, Vector3D b, float t)
			{
				return Vector3D.Interpolate(a, b, t);
			}
		}
	}

	public class QuaternionLinearKeyframesCompiler : LinearKeyframesCompiler<Quaternion>
	{
		protected override Quaternion ParseValue(string str)
		{
			return Quaternion.Parse(str);
		}

		protected override LinearKeyframes<Quaternion> CreateObject(float[] positions, Quaternion[] values, KeyframesAddressing addressing)
		{
			return new QuaternionLinearKeyframes(positions, values, addressing);
		}

		public override Type ObjectType
		{
			get { return typeof(QuaternionLinearKeyframes); }
		}
	}

	[Dxml.DidaObject(Compiler = typeof(QuaternionLinearKeyframesCompiler))]
	public class QuaternionLinearKeyframes : LinearKeyframes<Quaternion>
	{
		public QuaternionLinearKeyframes(float[] positions, Quaternion[] values,
			KeyframesAddressing addressing)
			: base(positions, values, addressing, new QuaternionInterpolator())
		{
		}

		private class QuaternionInterpolator : IInterpolator
		{
			public Quaternion Interpolate(Quaternion a, Quaternion b, float t)
			{
				if (Quaternion.DotProduct(a, b) < 0)
					b = -b;
				return Quaternion.Slerp(a, b, t);
			}
		}
	}
}